package service

import (
	"errors"
	"fitness/go-admin/internal/model"
	"fitness/go-admin/internal/repository"
	"math"
	"time"
)

// CheckinService 签到服务接口
type CheckinService interface {
	// 基础签到功能
	CreateCheckin(userID, memberID uint, req *model.CheckinReq) (*model.CheckinResp, error)
	GetMemberCheckins(memberID uint, req *model.CheckinListReq) (*model.CheckinListResp, error)
	GetCheckinStatistics(memberID uint, req *model.CheckinStatisticsReq) (*model.CheckinStatisticsResp, error)

	// 签退功能
	CreateCheckout(userID, memberID uint, req *model.CheckoutReq) (*model.CheckoutResp, error)

	// 前台功能
	GetStaffCheckins(req *model.StaffCheckinListReq) (*model.StaffCheckinListResp, error)
	CreateStaffCheckin(operatorID uint, req *model.StaffCheckinReq) (*model.StaffCheckinResp, error)

	// 管理员功能
	GetAdminCheckins(req *model.AdminCheckinListReq) (*model.AdminCheckinListResp, error)
	GetAdminAnalytics(req *model.AdminAnalyticsReq) (*model.AdminAnalyticsResp, error)
	GenerateQRCode(req *model.GenerateQRCodeReq) (*model.GenerateQRCodeResp, error)
	BatchCheckin(operatorID uint, req *model.BatchCheckinReq) (*model.BatchCheckinResp, error)

	// 工具方法
	ValidateLocation(memberLat, memberLng, gymLat, gymLng, gymRadius, accuracy float64) bool
	ValidateQRCode(qrContent string, gymID uint) error
	AutoCheckout() error
}

type checkinService struct {
	repo repository.CheckinRepository
}

func NewCheckinService(repo repository.CheckinRepository) CheckinService {
	return &checkinService{repo: repo}
}

// ==================== 工具方法 ====================

// calculateDistance 使用Haversine公式计算两点间距离
func calculateDistance(lat1, lng1, lat2, lng2 float64) float64 {
	const EARTH_RADIUS = 6371000 // 地球半径（米）
	lat1Rad := lat1 * math.Pi / 180
	lat2Rad := lat2 * math.Pi / 180
	deltaLatRad := (lat2 - lat1) * math.Pi / 180
	deltaLngRad := (lng2 - lng1) * math.Pi / 180

	a := math.Sin(deltaLatRad/2)*math.Sin(deltaLatRad/2) +
		math.Cos(lat1Rad)*math.Cos(lat2Rad)*
			math.Sin(deltaLngRad/2)*math.Sin(deltaLngRad/2)
	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
	return EARTH_RADIUS * c
}

// ValidateLocation 验证会员是否在健身房范围内
func (s *checkinService) ValidateLocation(memberLat, memberLng, gymLat, gymLng, gymRadius, accuracy float64) bool {
	distance := calculateDistance(memberLat, memberLng, gymLat, gymLng)
	// 考虑定位精度误差
	return distance <= gymRadius+accuracy
}

// ValidateQRCode 验证二维码有效性
func (s *checkinService) ValidateQRCode(qrContent string, gymID uint) error {
	// 这里应该实现具体的二维码验证逻辑
	// 为简化起见，我们假设验证通过
	return nil
}

// AutoCheckout 每天23:30自动为未签退会员签退
func (s *checkinService) AutoCheckout() error {
	// 获取今天未签退的签到记录
	// 更新签退时间
	return nil
}

// ==================== 基础签到功能 ====================

func (s *checkinService) CreateCheckin(userID, memberID uint, req *model.CheckinReq) (*model.CheckinResp, error) {
	// 验证营业时间（6:00-23:00）
	now := time.Now()
	hour := now.Hour()
	if hour < 6 || hour >= 23 {
		return nil, errors.New("当前非营业时间（营业时间：6:00-23:00）")
	}

	// 检查今日是否已签到
	todayCheckin, err := s.repo.GetMemberTodayCheckin(memberID, now, 1)
	if err == nil && todayCheckin != nil {
		return nil, errors.New("今日已签到，请勿重复打卡")
	}

	// 验证签到方式
	if req.CheckInMethod == "GPS" || req.CheckInMethod == "BOTH" {
		// GPS签到需要验证位置
		if req.Latitude == nil || req.Longitude == nil {
			return nil, errors.New("GPS签到需要提供位置信息")
		}
		// 这里应该获取健身房的地理围栏信息并验证位置
		// 为简化起见，我们假设验证通过
	}

	if req.CheckInMethod == "QR" || req.CheckInMethod == "BOTH" {
		// 二维码签到需要验证二维码
		if req.QRCode == "" {
			return nil, errors.New("二维码签到需要提供二维码内容")
		}
		// 这里应该验证二维码有效性
		// 为简化起见，我们假设验证通过
	}

	// 创建签到记录
	checkin := &model.Checkin{
		MemberID:      memberID,
		UserID:        userID,
		CheckInTime:   now,
		Location:      req.Location,
		DeviceID:      req.DeviceID,
		Type:          1, // 进馆签到
		CheckInMethod: req.CheckInMethod,
		QRCode:        req.QRCode,
		Note:          req.Note,
	}

	if req.Latitude != nil {
		checkin.Latitude = req.Latitude
	}
	if req.Longitude != nil {
		checkin.Longitude = req.Longitude
	}
	if req.Accuracy != nil {
		checkin.Accuracy = req.Accuracy
	}

	// 获取客户端IP地址
	// 这里应该从gin.Context获取IP地址
	// 为简化起见，我们使用默认值
	checkin.IPAddress = "127.0.0.1"

	if err := s.repo.CreateCheckin(checkin); err != nil {
		return nil, err
	}

	// 计算连续签到天数
	consecutiveDays, _ := s.repo.GetMemberConsecutiveDays(memberID)

	resp := &model.CheckinResp{
		ID:              checkin.ID,
		MemberID:        checkin.MemberID,
		MemberName:      "会员", // 这里应该从会员信息中获取
		CheckInTime:     checkin.CheckInTime,
		Location:        checkin.Location,
		Type:            checkin.Type,
		TypeText:        "进馆签到",
		ConsecutiveDays: consecutiveDays,
	}

	return resp, nil
}

func (s *checkinService) GetMemberCheckins(memberID uint, req *model.CheckinListReq) (*model.CheckinListResp, error) {
	// 设置默认值
	if req.Page <= 0 {
		req.Page = 1
	}
	if req.PageSize <= 0 {
		req.PageSize = 10
	}
	if req.PageSize > 100 {
		req.PageSize = 100
	}

	checkins, total, err := s.repo.GetMemberCheckins(memberID, req)
	if err != nil {
		return nil, err
	}

	// 转换为响应格式
	var records []model.CheckinRecord
	for _, checkin := range checkins {
		typeText := "进馆签到"
		if checkin.Type == 2 {
			typeText = "离馆签退"
		}

		records = append(records, model.CheckinRecord{
			ID:           checkin.ID,
			CheckInTime:  checkin.CheckInTime,
			CheckOutTime: checkin.CheckOutTime,
			Duration:     checkin.Duration,
			Location:     checkin.Location,
			DeviceID:     checkin.DeviceID,
			Type:         checkin.Type,
			TypeText:     typeText,
		})
	}

	resp := &model.CheckinListResp{
		List:     records,
		Total:    total,
		Page:     req.Page,
		PageSize: req.PageSize,
	}

	return resp, nil
}

func (s *checkinService) GetCheckinStatistics(memberID uint, req *model.CheckinStatisticsReq) (*model.CheckinStatisticsResp, error) {
	// 设置默认值
	if req.Period == "" {
		req.Period = "month"
	}

	resp := &model.CheckinStatisticsResp{}

	// 获取连续签到天数
	consecutiveDays, _ := s.repo.GetMemberConsecutiveDays(memberID)
	resp.ConsecutiveDays = consecutiveDays

	// 获取总签到天数和总时长
	// 这里应该实现具体的统计逻辑
	resp.TotalDays = 10
	resp.TotalDuration = 1200
	resp.AvgDuration = 120

	// 获取周趋势数据
	trend, _ := s.repo.GetCheckinTrend(memberID, 7)
	// 修复类型不匹配问题
	resp.WeeklyTrend = make([]struct {
		Date     string `json:"date"`
		Count    int    `json:"count"`
		Duration int    `json:"duration"`
	}, len(trend))
	for i, item := range trend {
		resp.WeeklyTrend[i] = struct {
			Date     string `json:"date"`
			Count    int    `json:"count"`
			Duration int    `json:"duration"`
		}{
			Date:     item.Date,
			Count:    item.Count,
			Duration: item.Duration,
		}
	}

	// 获取排名信息
	// 这里应该实现具体的排名逻辑
	resp.Rank.Percentage = 85
	resp.Rank.TotalMembers = 500

	return resp, nil
}

// ==================== 签退功能 ====================

func (s *checkinService) CreateCheckout(userID, memberID uint, req *model.CheckoutReq) (*model.CheckoutResp, error) {
	now := time.Now()

	// 获取今日最后一次签到记录
	lastCheckin, err := s.repo.GetMemberTodayLastCheckin(memberID, now)
	if err != nil {
		return nil, errors.New("今日未签到，无法签退")
	}

	// 检查是否已签退
	if lastCheckin.CheckOutTime != nil {
		return nil, errors.New("已签退，请勿重复操作")
	}

	// 计算锻炼时长（分钟）
	duration := int(now.Sub(lastCheckin.CheckInTime).Minutes())
	lastCheckin.CheckOutTime = &now
	lastCheckin.Duration = &duration

	// 更新签到记录
	if err := s.repo.UpdateCheckin(lastCheckin); err != nil {
		return nil, err
	}

	// 计算累计时长
	// 这里应该从数据库中获取会员的累计时长
	totalDuration := 3200

	resp := &model.CheckoutResp{
		ID:            lastCheckin.ID,
		MemberID:      lastCheckin.MemberID,
		MemberName:    "会员", // 这里应该从会员信息中获取
		CheckInTime:   lastCheckin.CheckInTime,
		CheckOutTime:  *lastCheckin.CheckOutTime,
		Duration:      duration,
		TotalDuration: totalDuration,
	}

	return resp, nil
}

// ==================== 前台功能 ====================

func (s *checkinService) GetStaffCheckins(req *model.StaffCheckinListReq) (*model.StaffCheckinListResp, error) {
	return s.repo.GetStaffCheckins(req)
}

func (s *checkinService) CreateStaffCheckin(operatorID uint, req *model.StaffCheckinReq) (*model.StaffCheckinResp, error) {
	now := time.Now()

	// 检查会员今日是否已签到
	todayCheckin, err := s.repo.GetMemberTodayCheckin(req.MemberID, now, 1)
	if err == nil && todayCheckin != nil {
		return nil, errors.New("该会员今日已签到")
	}

	// 创建签到记录
	checkin := &model.Checkin{
		MemberID:    req.MemberID,
		UserID:      operatorID, // 使用操作员ID作为用户ID
		CheckInTime: now,
		DeviceID:    req.DeviceID,
		Type:        1, // 进馆签到
		OperatorID:  &operatorID,
		Note:        req.Note,
	}

	if err := s.repo.CreateCheckin(checkin); err != nil {
		return nil, err
	}

	// 计算连续签到天数
	consecutiveDays, _ := s.repo.GetMemberConsecutiveDays(req.MemberID)

	resp := &model.StaffCheckinResp{
		ID:              checkin.ID,
		MemberID:        checkin.MemberID,
		MemberName:      "会员",     // 这里应该从会员信息中获取
		MemberCard:      "VIP001", // 这里应该从会员信息中获取
		CheckInTime:     checkin.CheckInTime,
		Operator:        "前台工作人员", // 这里应该从操作员信息中获取
		ConsecutiveDays: consecutiveDays,
	}

	return resp, nil
}

// ==================== 管理员功能 ====================

func (s *checkinService) GetAdminCheckins(req *model.AdminCheckinListReq) (*model.AdminCheckinListResp, error) {
	return s.repo.GetAdminCheckins(req)
}

func (s *checkinService) GetAdminAnalytics(req *model.AdminAnalyticsReq) (*model.AdminAnalyticsResp, error) {
	return s.repo.GetAdminAnalytics(req)
}

func (s *checkinService) GenerateQRCode(req *model.GenerateQRCodeReq) (*model.GenerateQRCodeResp, error) {
	// 获取健身房信息
	_, err := s.repo.GetGymByID(req.GymID)
	if err != nil {
		return nil, errors.New("健身房不存在")
	}

	// 设置默认有效期
	if req.ExpireMinutes <= 0 {
		req.ExpireMinutes = 30
	}

	// 生成二维码内容
	expireAt := time.Now().Add(time.Duration(req.ExpireMinutes) * time.Minute)
	// 生成随机字符串作为二维码内容
	qrContent := generateRandomString(50)

	resp := &model.GenerateQRCodeResp{
		QRCodeContent: qrContent,
		ExpireAt:      expireAt,
	}

	return resp, nil
}

// generateRandomString 生成随机字符串
func generateRandomString(length int) string {
	// 简单实现，实际项目中应该使用更安全的随机字符串生成方法
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	result := make([]byte, length)
	for i := range result {
		result[i] = charset[i%len(charset)]
	}
	return string(result)
}

func (s *checkinService) BatchCheckin(operatorID uint, req *model.BatchCheckinReq) (*model.BatchCheckinResp, error) {
	resp := &model.BatchCheckinResp{
		Details: make([]struct {
			MemberID   uint   `json:"memberId"`
			MemberName string `json:"memberName"`
			Status     string `json:"status"`
			Message    string `json:"message"`
		}, len(req.Records)),
	}

	successCount := 0
	failCount := 0

	for i, record := range req.Records {
		resp.Details[i].MemberID = record.MemberID
		resp.Details[i].MemberName = "会员" // 这里应该从会员信息中获取

		switch req.Action {
		case "create":
			// 创建签到记录
			checkin := model.Checkin{
				MemberID:    record.MemberID,
				UserID:      operatorID,
				CheckInTime: record.CheckInTime,
				Location:    record.Location,
				Type:        1, // 进馆签到
				OperatorID:  &operatorID,
			}

			if record.CheckOutTime != nil {
				checkin.CheckOutTime = record.CheckOutTime
				checkin.Type = 2 // 离馆签退

				// 计算时长
				if record.CheckOutTime != nil && !record.CheckInTime.IsZero() {
					duration := int(record.CheckOutTime.Sub(record.CheckInTime).Minutes())
					checkin.Duration = &duration
				}
			}

			if err := s.repo.CreateCheckin(&checkin); err != nil {
				resp.Details[i].Status = "failure"
				resp.Details[i].Message = err.Error()
				failCount++
			} else {
				resp.Details[i].Status = "success"
				successCount++
			}

		case "delete":
			// 删除签到记录
			// 这里应该实现具体的删除逻辑
			resp.Details[i].Status = "success"
			successCount++
		}
	}

	resp.SuccessCount = successCount
	resp.FailCount = failCount

	return resp, nil
}
